﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Collections.ObjectModel;
using System.Data;

using DbSharp.Framework.DbObject;
using DbSharp.Framework.Common;
using DbSharp.Framework.Driver;

namespace DbSharp.Framework.CaseFile.Parser
{
    /// <summary>
    /// Extract all sqlDeclaration objects from case file
    /// </summary>
    class SqlDeclarationExtractor
    {
        #region Private members

        XElement m_SqlDeclarationsRootElement = null;
        DbConnectionConfigExtractor m_ConnConfigExtractor;
        List<SqlDeclaration> m_ListSqlDeclaration = new List<SqlDeclaration>();

        #endregion

        public SqlDeclarationExtractor(XElement sqlDeclarationsRootElement, DbConnectionConfigExtractor connectionConfigExtractor)
        {
            if (sqlDeclarationsRootElement != null)
            {
                if (connectionConfigExtractor == null)
                    throw new ArgumentNullException("connectionConfigExtractor");
            }
            m_SqlDeclarationsRootElement = sqlDeclarationsRootElement;
            m_ConnConfigExtractor = connectionConfigExtractor;
            Extract();
        }


        #region Private methods

        /// <summary>
        /// check whether alias of of SqlDeclaration is non-empty and unique
        /// </summary>
        /// <param name="list"></param>
        private void ValidateAlias(IEnumerable<SqlDeclaration> list)
        {
            var listNames = new HashSet<string>();
            foreach (var item in list)
            {
                if (item.Alias.Length == 0)
                    throw new ArgumentException("SqlDeclaration.Alias cannot be an empty string.");

                //check whether each of Alias is unique
                if (false == listNames.Add(item.Alias))
                    throw new DuplicateNameException(string.Format("The Alias {0} is already present, please rename with another string.", item.Alias));
            }
        }


        private void Extract()
        {
            m_ListSqlDeclaration.Clear();
            if (m_SqlDeclarationsRootElement == null)
                return;

            var q = from statementSection in m_SqlDeclarationsRootElement.Elements("sqlDeclaration")
                    let aliasAttribute = statementSection.Attribute("alias")
                    let dbConnection = statementSection.Attribute("dbConnection").Value.Trim()
                    let dbConnConfig = m_ConnConfigExtractor[dbConnection]
                    let sqlElement = statementSection.Element("sql")
                    let remarkElmt = statementSection.Element("remark")
                    let argsRootElmt = statementSection.Element("arguments")
                    let argsExtractor = new StatementDeclarationArgsExtractor(argsRootElmt, dbConnConfig.ProviderName, dbConnConfig.ConnectionString, true)
                    select new
                    {
                        SqlDeclaration = new SqlDeclaration
                        {
                            Alias = aliasAttribute.Value.Trim(),
                            CommandText = sqlElement.Value,
                            ListArgument = argsExtractor.ListArgument.ToList(),
                            ConnectionString = dbConnConfig.ConnectionString,
                            ConnectionProviderName = dbConnConfig.ProviderName,
                            Remark = (remarkElmt == null) ? string.Empty : remarkElmt.Value.ToString()
                        }
                    };

            m_ListSqlDeclaration.Clear();
            foreach (var item in q)
            {
                SqlDeclaration sql = item.SqlDeclaration;
                m_ListSqlDeclaration.Add(sql);
            }

            //validate the procedure alias unique
            ValidateAlias(m_ListSqlDeclaration);


            
        }

        #endregion


        #region Public methods/properties


        public ReadOnlyCollection<SqlDeclaration> ListSqlDeclaration
        {
            get
            {
                return new ReadOnlyCollection<SqlDeclaration>(m_ListSqlDeclaration);
            }
        }

        public SqlDeclaration this[string statementAlias]
        {
            get
            { 
                if (string.IsNullOrEmpty(statementAlias))
                    throw new ArgumentException("statementAlias must be assigned.");

                string upperName = statementAlias.Trim().ToUpper();
                if (upperName.Length == 0)
                    throw new ArgumentException("statementAlias cannot be an empty string.");

                foreach (SqlDeclaration item in m_ListSqlDeclaration)
                {
                    if (item.Alias.ToUpper() == upperName)
                        return item;
                }

                string msgFmt = "Failed to find one sqlDeclaration which name is '{0}'.";
                string msg = string.Format(msgFmt, statementAlias);
                throw new DbSharpException(msg);
            }
        }

        #endregion


    }
}
